
#include <BStyle>
#include <BPainter>
#include <BTextEdit>
#include <BSonDocument>

#include "SonFormat.h"

SonFormat::SonFormat(const BString& name) : BTextFormat(name)
{
	_textEdit = 0;
	_treeWidget = 0;

	_topLayout = new BHBoxLayout();
	_topLayout->setPart(Part_Top);
	{
		_structureButton = new BButton("structure");
		_structureButton->setCheckable(true);
		_topLayout->addSpring();
		_topLayout->addWidget(_structureButton);
	}

	_dockLayout = new BDockLayout();
	_dockLayout->setPart(Part_Center);
	_dockLayout->setSplitable(true);
	_dockLayout->setSpacing(5);

	connect(this, Signal_AssetChanged, &SonFormat::slotAssetChanged);
	connect(_structureButton, Signal_Toggled, &SonFormat::slotStructureButtonToggled);
}
SonFormat::~SonFormat()
{

}

void SonFormat::styled(const BStyle* style)
{
	_style = style;
	_colorMap[SType_Item].set(0, 0, 128);
	_colorMap[SType_Begin].set(0, 100, 200);
	_colorMap[SType_End].set(0, 100, 200);
	_colorMap[SType_Name].set(128, 0, 128);
	_colorMap[SType_Value].set(128, 0, 0);
	_colorMap[SType_Annotation].set(0, 128, 0);

	if (style)
	{
		if (style->hasColor("type-item"))
			_colorMap[SType_Item] = style->color("type-item");
		if (style->hasColor("type-begin"))
			_colorMap[SType_Begin] = style->color("type-begin");
		if (style->hasColor("type-end"))
			_colorMap[SType_End] = style->color("type-end");
		if (style->hasColor("type-name"))
			_colorMap[SType_Name] = style->color("type-name");
		if (style->hasColor("type-value"))
			_colorMap[SType_Value] = style->color("type-value");
		if (style->hasColor("type-annotation"))
			_colorMap[SType_Annotation] = style->color("type-annotation");
	}

	this->emit(Signal_Styled, style);
}
void SonFormat::changed(const BString& sontext)
{
	_itemMap.reset();
	_topItems.clear();
	if (sontext.size())
	{
		BString name;
		auto begin = sontext.begin();
		auto cit = sontext.begin();
		while (cit.valid())
		{
			if (cit == '{')
			{
				auto end = cit;
				trimmedString(begin, end);
				BString text = sontext.substr(begin, end);
				SonItem* item = insertSonItem(begin.count(), end.count() - begin.count(), SType_Item);
				SonItem* beginItem = insertSonItem(cit.count(), 1, SType_Begin, item);
				readSonItem(item, sontext, ++cit, 0);
				_topItems.append(item);
				continue;
			}
			cit++;
		}
	}
	freshStructure();
}

const BFont* SonFormat::font(int pos) const
{
	return 0;
}
const BColor* SonFormat::back(int pos) const
{
	return 0;
}
const BColor* SonFormat::color(int pos) const
{
	auto it = _itemMap <= pos;
	if (SonItem* item = it.value())
	{
		const BColor& color = _colorMap(item->type);
		if (pos < item->range.max() && color.a() > 0)
			return &color;
	}
	return 0;
}

void SonFormat::freshStructure()
{
	if (_treeWidget)
	{
		_treeWidget->clearItems();
		BSonDocument document;
		if (document.read(_textEdit->text()))
		{
			for (int i = 0; i < document.nodeCount(); i++)
			{
				BSonNode* node = document.node(i);
				_treeWidget->addItem(createTreeItem(node));
			}
		}
	}
}

int SonFormat::nextWord(BString& word, const BString& text, int& pos)
{
	char c = 0;
	while (pos < text.size())
	{
		c = text[pos];
		if (!isspace(c))
			break;
		pos++;
	}
	if (pos == text.size())
	{
		word.reset();
		return text.size();
	}
	int begin = pos;
	if (c == ':' && text[pos + 1] == ':')
	{
		word = "::";
		pos += 2;
		return begin;
	}
	if (c == '\"')
	{
		int begin = pos++;
		pos = text.find('\"', pos);
		word.reset(text, begin, ++pos);
		return begin;
	}
	if (c == '$' || c == '@')
	{
		int begin = pos++;
		pos = text.word(word, pos);
		word.prepend(c);
		return begin;
	}
	if (c == '+' && text[pos + 1] == '+')
	{
		word = "++";
		pos += 2;
		return begin;
	}
	if (c == '-' && text[pos + 1] == '-')
	{
		word = "--";
		pos += 2;
		return begin;
	}
	if (c == '.' || c == ',' || c == ';' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}')
	{
		word = c;
		pos += 1;
		return begin;
	}
	if (c == '<' || c == '>' || c == '!' || c == '+' || c == '-' || c == '*' || c == '/' || c == '|')
	{
		word = c;
		if (text[pos + 1] == '=')
		{
			word << '=';
			pos += 2;
			return begin;
		}
		pos++;
		return begin;
	}
	if (isdigit(text[pos]))
	{
		int end = pos;
		while (++end < text.size())
		{
			if (text[end] == '.' && !isdigit(text[end + 1]))
				break;
			if (!isdigit(text[end]))
				break;
		}
		word.reset(text, begin, end);
		pos = end;
		return begin;
	}
	if (ispunct(text[pos]))
	{
		int end = pos;
		while (end < text.size())
		{
			if (isspace(text[end]))
				break;
			if (!ispunct(text[end]))
				break;
			end++;
		}
		word.reset(text, begin, end);
		pos = end;
		return begin;
	}
	while (pos < text.size())
	{
		if (isspace(text[pos]))
			break;
		if (ispunct(text[pos]))
			break;
		pos++;
	}
	word.reset(text, begin, pos);
	return begin;
}
SonFormat::SonItem* SonFormat::insertSonItem(int pos, int count, SonType type, SonItem* parent)
{
	SonItem* item = new SonItem();
	item->type = type;
	item->range.min() = pos;
	item->range.max() = pos + count;
	if (parent)
	{
		item->parent = parent;
		parent->children.append(item);
	}
	_itemMap.insert(pos, item);
	return item;
}
BRange SonFormat::trimmedString(BString::const_iterator& begin, BString::const_iterator& end)
{
	if (begin < end)
	{
		while (begin < end)
		{
			if (!begin->isSpace())
				break;
			++begin;
		}
		while (begin < --end)
		{
			if (!end->isSpace())
			{
				end++;
				break;
			}
		}
	}
	return BRange(begin.count(), end.count());
}
bool SonFormat::readSonItem(SonItem* item, const BString& sontext, BString::const_iterator& cit, int level)
{
	auto begin = cit;
	auto equal = sontext.end();
	auto prev = sontext.end();
	while (cit.valid())
	{
		if (cit == '\\')
		{
			cit += 2;
			continue;
		}
		if (cit == '/' && prev == '/')
		{
			auto end = sontext.find('\n', cit);
			SonItem* child = insertSonItem(prev.count(), end.count() - prev.count(), SType_Annotation, item);
			begin = cit = ++end;
			prev.clear();
			continue;
		}
		if (cit == '*' && prev == '/')
		{
			auto end = sontext.find("*/", cit);
			if (end.valid())
				end += 2;
			SonItem* child = insertSonItem(prev.count(), end.count() - prev.count(), SType_Annotation, item);
			begin = cit = ++end;
			prev.clear();
			continue;
		}
		if (cit == '=' && equal.empty())
		{
			equal = cit++;
			continue;
		}
		if (cit == ';')
		{
			if (equal.valid())
			{
				auto end = equal;
				trimmedString(begin, end);
				insertSonItem(begin.count(), end.count() - begin.count(), SType_Name, item);

				auto value_begin = equal + 1;
				auto value_end = cit;
				trimmedString(value_begin, value_end);
				insertSonItem(value_begin.count(), value_end.count() - value_begin.count(), SType_Value, item);
				equal.clear();
			}
			begin = ++cit;
			continue;
		}
		if (cit == '}')
		{
			SonItem* endItem = insertSonItem(cit.count(), 1, SType_End, item);
			begin = ++cit;
			return true;
		}
		if (cit == '{')
		{
			auto end = cit;
			trimmedString(begin, end);
			SonItem* child = insertSonItem(begin.count(), end.count() - begin.count(), SType_Item, item);
			SonItem* beginItem = insertSonItem(cit.count(), 1, SType_Begin, child);
			readSonItem(child, sontext, ++cit, level + 1);
			begin = ++cit;
			continue;
		}
		if (cit == ';')
		{
			begin = ++cit;
			continue;
		}
		if (cit == '}')
		{
			return true;
		}
		prev = cit++;
	}
	return false;
}

BTreeItem* SonFormat::createTreeItem(BSonNode* node)
{
	const BStyle* style = _treeWidget->realStyle();
	BTreeItem* item = new BTreeItem(node->name());
	item->setAsset(node);
	item->setImage(style->image("node"));

	const BMap<BString, BString>& attribMap = node->attribMap();
	for (int i = 0; i < attribMap.size(); i++)
	{
		const BString& key = attribMap.key(i);
		const BString& value = attribMap.value(i);
		BTreeItem* child = item->addChild(key + ':' + value);
		child->setImage(style->image("attrib"));
	}

	if (node->text().size())
	{
		BString text = node->text();
		BTreeItem* child = item->addChild(text);
		child->setImage(style->image("text"));
	}

	for (int i = 0; i < node->childCount(); i++)
	{
		BTreeItem* child = createTreeItem(node->child(i));
		item->addChild(child);
	}
	return item;
}

void SonFormat::slotAssetChanged(BObject* object, const BValue& value)
{
	BTextEdit* textEdit = value;
	if (_textEdit != textEdit)
	{
		if (_textEdit)
		{
			disconnect(_textEdit, Signal_Framed);
			_textEdit->removeLayout(_topLayout);
			_textEdit->removeLayout(_dockLayout);
		}
		_textEdit = textEdit;
		if (_textEdit)
		{
			//connect(_textEdit, Signal_Framed, &SonFormat::slotTextEditFramed);
			_textEdit->addLayout(_topLayout);
			_textEdit->addLayout(_dockLayout);
		}
		_dirty = true;
		if (_treeWidget)
		{
			_treeWidget->clearItems();
		}
	}
}
void SonFormat::slotTextEditFramed(BTextEdit* textEdit, const BValue& value)
{
	if (_textEdit)
	{
		const BIntArray& lines = textEdit->query("visual-lines");
		if (lines.size())
		{
			BPainter painter(textEdit);
			painter.setColor(Color_Text);

			BRange firstRange = textEdit->lineRange(lines.first());
			BRange lastRange = textEdit->lineRange(lines.last());
			auto begin = _itemMap >= firstRange.min();
			auto end = _itemMap > lastRange.max();
			if (begin.valid() && begin < end)
			{
				for (auto it = begin; it < end; it++)
				{
					SonItem* item = *it;
					if (it.key() == 6)
						int a = 0;
					BRect rect = textEdit->codeRect(it.key());
					for (int i = item->range.min(); i < item->range.max(); i++)
					{
						BRect codeRect = textEdit->codeRect(i);
						rect.expand(codeRect);
					}
					painter.drawRect(rect);
				}
			}
		}
	}
}
void SonFormat::slotStructureButtonToggled(BObject* object, const BValue& value)
{
	if (value)
	{
		_treeWidget = new BTreeWidget();
		_treeWidget->setSizePolicy(Policy_Growing);
		_dockLayout->setWidthStretch(100);
		_dockLayout->addWidget(_treeWidget, Align_Right);
		_dockLayout->setStretch(_treeWidget, BSizeStretch(0.1f, 1.0f));
		freshStructure();
	}
	else
	{
		_dockLayout->remove(_treeWidget);
		_treeWidget = 0;
	}
}